﻿using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Logging;
using PMS.Web.Framework.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

namespace PMS.Web.Framework.Mvc.ModelBinding
{
    /// <summary>
    /// Represents model binder for the binding models inherited from the BaseNopModel
    /// </summary>
    public class EntityModelBinder : ComplexTypeModelBinder
    {
        #region Properties
        public BodyModelBinder BodyModelBinder { get; set; }
        #endregion

        #region Ctor

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="propertyBinders">Property binders</param>
        public EntityModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
            : base(propertyBinders, loggerFactory)
        {
        }

        #endregion

        #region Methods

        /// <summary>
        /// Create model for given binding context
        /// </summary>
        /// <param name="bindingContext">Model binding context</param>
        /// <returns>Model</returns>
        protected override object CreateModel(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException(nameof(bindingContext));

            //DavidLee : 两种方式尝试绑定数据模型
            //1: try to create model by using ComplexTypeModelBinder
            var model = base.CreateModel(bindingContext);
            //2: try to create model by getting from Body Source
            if(model == null || !bindingContext.Result.IsModelSet)
            {
                if (BodyModelBinder != null)
                {
                    BodyModelBinder.BindModelAsync(bindingContext).Wait();
                    if (bindingContext.Result.IsModelSet)
                    {
                        model = bindingContext.Result.Model;
                        //trim property string values for BaseEntityModel
                        PropertyInfo[] modelProps = model.GetType().GetProperties();
                        foreach (PropertyInfo pi in modelProps)
                        {
                            if (!pi.IsDefined(typeof(NoTrimAttribute), false)) continue;
                            // 获取属性的值
                            var valueAsString = pi.GetValue(model);
                            if(valueAsString is string)
                            {
                                if (!string.IsNullOrEmpty((string)valueAsString))
                                    pi.SetValue(model, Regex.Replace((string)valueAsString, @"\s+", "").Trim(), null);
                            }
                        }
                    }
                }
            }

            //add custom model binding
            if (model is BaseEntityModel)
                (model as BaseEntityModel).BindModel(bindingContext);

            return model;
        }

        /// <summary>
        ///  Updates a property in the current model
        /// </summary>
        /// <param name="bindingContext">Model binding context</param>
        /// <param name="modelName">The model name</param>
        /// <param name="propertyMetadata">The model metadata for the property to set</param>
        /// <param name="bindingResult">The binding result for the property's new value</param>
        protected override void SetProperty(ModelBindingContext bindingContext, string modelName,
            ModelMetadata propertyMetadata, ModelBindingResult bindingResult)
        {
            if (bindingContext == null)
                throw new ArgumentNullException(nameof(bindingContext));

            //trim property string values for entity models
            var valueAsString = bindingResult.Model as string;
            if (bindingContext.Model is BaseEntityModel && !string.IsNullOrEmpty(valueAsString))
            {
                //excluding properties with [NoTrim] attribute
                var noTrim = (propertyMetadata as DefaultModelMetadata)?.Attributes?.Attributes?.OfType<NoTrimAttribute>().Any();
                if (!noTrim.HasValue || !noTrim.Value)
                    bindingResult = ModelBindingResult.Success(Regex.Replace(valueAsString, @"\s+", "").Trim());
            }

            base.SetProperty(bindingContext, modelName, propertyMetadata, bindingResult);
        }

        #endregion
    }

}
